const Koa = require("koa");
const KoaBody = require("koa-body");
const errorHandler = require("./middlewares/error");
const KoaCors = require("koa2-cors");
const getPort = require("get-port");
const requireDir = require("require-dir");
const { AuthDefaultMethods, JwtMiddleWare } = require("./middlewares/auth");
const LevenxRoute = require("./middlewares/route");
const mysqlInit = require("./middlewares/mysqlDB");
const redisInit = require("./middlewares/redis");
const koaLogger = require("./middlewares/log4j");
const koaMail = require("./middlewares/mail");
const portal = require("./middlewares/services/portal");

module.exports = class Server extends Koa {
  constructor(options) {
    super();
    options = options || {};
    this.options = options;
    const { appPrefix, cors, routeDir, jwt, appNmae, log, mail } = options;

    //参数校验
    if (!routeDir) {
      throw new Error("路由文件夹需要创建,可参考npm文档");
    }

    //中间件处理
    if (jwt) {
      this.use(errorHandler());
    }

    //日志
    if (log) {
      const { filename, level } = log;
      this.use(
        koaLogger(
          {
            type: "dateFile", //按日期创建文件，文件名为 filename + pattern
            filename: filename || `logs/${appNmae || "app"}`,
            pattern: "yyyy-MM-dd.log",
            alwaysIncludePattern: true,
          },
          {
            env: this.env || "development", //如果是development则可以同时在控制台中打印
            level: level || "info",
          }
        )
      );
    }

    //邮件
    if (mail) {
      const { host, service, auth, from } = mail;
      this.use(koaMail({ host, service, auth, from }));
    }

    // POST请求体处理
    this.use(
      KoaBody({
        multipart: true,
        formidable: {
          maxFileSize: 200 * 1024 * 1024, // 设置上传文件大小最大限制，默认2M
        },
      })
    );

    // 跨域
    const origin = "*";
    if (cors && cors.origin) {
      origin = cors.origin;
    }
    this.use(
      KoaCors({
        origin,
        maxAge: 5, //指定本次预检请求的有效期，单位为秒。
        credentials: true, //是否允许发送Cookie
        allowMethods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"], //设置所允许的HTTP请求方法
        allowHeaders: ["Content-Type", "Authorization", "Accept"], //设置服务器支持的所有头信息字段
        exposeHeaders: ["WWW-Authenticate", "Server-Authorization"], //设置获取其他自定义字段
      })
    );

    this.use(portal({ appPrefix }));
  }

  //启动
  start(port, callback) {
    const { appPrefix, routeDir, jwt, mysql, redis } = this.options;
    let redisInstance, mysqlInstance;
    if (redis) {
      const { host, port, password, db, prefix } = redis;
      redisInstance = redisInit({ host, port, password, db, prefix });
    }

    if (mysql) {
      const { modelDir, database, host, username, password } = mysql;
      const models = requireDir(modelDir);
      mysqlInstance = mysqlInit({
        database,
        host,
        username,
        password,
      });
      for (let model of Object.values(models)) {
        const { name, tableName, timestamps, freezeTableName, colums } = model;
        const Model = mysqlInstance.define(
          name,
          { ...colums },
          { tableName, timestamps, freezeTableName }
        );
        Model.sync({ force: true });
      }
    }

    //路由集中处理
    const router = new LevenxRoute({
      appPrefix,
      routeDir,
      mysqlInstance,
      redisInstance,
    });

    //权限
    if (jwt) {
      const { secret } = jwt;
      if (!secret) {
        throw new Error("Jwt secret 需要配置");
      }
      this.use(
        JwtMiddleWare({
          secret,
          verify: AuthDefaultMethods.verify,
          unAuths: [...router.loadUnAuths(), "/"],
        })
      );
    }

    router.loadRoutes().forEach((route) => {
      this.use(route);
    });

    (async () => {
      let newPort = await getPort({ port });
      if (newPort !== port) {
        newPort = await getPort({ port: getPort.makeRange(3000, 65536) });
        console.table({ old: port, new: newPort });
        port = newPort;
      }
      port = newPort;
      this.listen(port, () => {
        console.log(`App started on port: ${port}`);
        callback && callback();
      });
    })();
  }
};
